/**
 * 
 */
package xiaojian.toolkit.fsm;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import xiaojian.toolkit.ebus.EventBus;
import xiaojian.toolkit.fsm.event.TimeoutEvent;
import xiaojian.toolkit.util.DefaultPropertiesSupport;


/**
 * @author hp
 *
 */
public class DefaultFSMContext extends DefaultPropertiesSupport 
	implements MutableFSMContext {
	
	protected	final long	createTime = System.currentTimeMillis();
	protected	long		lastModify = System.currentTimeMillis();
	protected	final FiniteStateMachine	fsm;
	private	String			currentStateName;
	private	Object			reason;
	private	Object			key;
	private	Object 			snapshot;
	private	int				stamp;
	private	final AtomicReference<ExecutorService>	serviceRef = 
		new AtomicReference<ExecutorService>();
	
	private	Runnable	canceler = null;
	private	EventBus	eventBus;
	
	private	final AtomicBoolean	alive = new AtomicBoolean(true);
	private	String		eventOfDestroy = null;
	
	/**
	 * @return the eventOfDestroy
	 */
	public String getEventOfDestroy() {
		return eventOfDestroy;
	}

	/**
	 * @param eventOfDestroy the eventOfDestroy to set
	 */
	public void setEventOfDestroy(final String eventOfDestroy) {
		this.eventOfDestroy = eventOfDestroy;
	}

	/**
	 * @return the eventBus
	 */
	public EventBus getEventBus() {
		return eventBus;
	}

	/**
	 * @param eventBus the eventBus to set
	 */
	public void setEventBus(final EventBus eventBus) {
		this.eventBus = eventBus;
	}

	public DefaultFSMContext(final FiniteStateMachine fsm) {
		this.fsm = fsm;
	}
	
	/* (non-Javadoc)
	 * @see com.skymobi.fsm.FSMContext#getCurrentState()
	 */
	public String getCurrentState() {
		return this.currentStateName;
	}
	
	/* (non-Javadoc)
	 * @see com.skymobi.fsm.MutableFSMContext#setCurrentState(java.lang.String)
	 */
	public void setCurrentState(final String stateName) {
		if ( !this.isDestroyed() ) {
			if ( this.currentStateName != stateName ) {
				cancelTimer();
				this.currentStateName = stateName;
				lastModify = System.currentTimeMillis();
			}
		}
		else {
			throw new RuntimeException("invalid call setCurrentState method for dead Context");
		}
	}

	/* (non-Javadoc)
	 * @see com.skymobi.fsm.FSMContext#destroy()
	 */
	public List<Object> destroy() {
		alive.set(false);
		snapshot= null;
		serviceRef.set(null);
		lastModify = System.currentTimeMillis();
		if ( null != eventBus && null != eventOfDestroy ) {
			eventBus.fireEvent(eventOfDestroy, this);
		}
		return null;
	}

	public Object getEndReason() {
		return reason;
	}

	public void setEndReason(final Object reason) {
		this.reason = reason;
	}

	public void setKey(final Object key) {
		this.key = key;
	}

	public void setService(final ExecutorService service) {
		this.serviceRef.set(service);
	}

	public void setSnapshot(final Object snapshot) {
		this.snapshot = snapshot;
	}

	public Object getKey() {
		return key;
	}

	public ExecutorService getService() {
		return serviceRef.get();
	}

	public Object getSnapshot() {
		return snapshot;
	}

	public void cancelTimer() {
		if ( null != canceler ) {
			canceler.run();
			canceler = null;
		}
		++stamp;
	}

	public void clearTimer() {
		canceler = null;
	}
	
	public void startTimer(final TimeoutEvent event, final long delay) {
		event.setStamp(++stamp);
		canceler = fsm.scheduleTimeout(event,  delay);
	}

	public boolean isTimeoutActive(final TimeoutEvent event) {
		return (event.getStamp() == stamp);
	}

	public long getCreateTime() {
		return createTime;
	}

	public long getLastModify() {
		return lastModify;
	}

	public void fireEventWithTimeout(final TimeoutEvent tm, final long timeout,
			final String event, final Object... args) {
		if ( null != eventBus && null != event ) {
			eventBus.fireEvent(event, args);
		}
    	if ( timeout > 0 ) {
            startTimer(tm, timeout);
    	}
	}

	public boolean isDestroyed() {
		return !alive.get();
	}


	public long getTimeToLive() {
		return	isDestroyed() 
				? (lastModify - createTime) 
				: (System.currentTimeMillis() - createTime);
	}

}
